/**
 * 191214：从旧库抽离
 * 200909：改进延迟执行函数waitToExecuteFrame，waitToExecuteTime，及cancelExecuteDelay
 * 200924：增加screenPointToWorld的输出参数
 * 201012：更正200909未顾及首执行和尾执行的错误
 * 201014：扩展clamp方法，区间上下限位置可随意
 * 201202：新增select方法
 * 210208：新增线性对照插值valueLinearMapping，未严格测试
 * 210510：新增setCaster中event的空值判断，以便直接使用输入pointer
 * 210910：新增创建和终止协程对象的方法
 * 220121：增加isGeneralArray方法
 * 20220304:[require] to [import]...jordia
 * 20220417:[instanceof Vector2 -> .isVector2]
 * 20220627:add function [decimalToString]
 */
import {
    Vector2,Vector3,Raycaster,Matrix4,Plane,Vector4,Matrix3,Color,Quaternion,Euler,Skeleton,
    Scene,LoopRepeat,MathUtils,AnimationMixer,Audio
} from 'three';

const D2R=Math.PI/180;
const MIN_FLOAT=1e-12;
const V_ARRAY=[];
const COMPARE_VALUE_DECREASE=(a,b)=>{
    return a<b;
};
const COMPARE_EQUAL_DEFAULT=(a,b)=>{
    return a===b;
};
const SORT_ARRAY=(ary, compareFunc, lo, hi)=> {
    let i = lo, j = hi;
    let x = ary[Math.ceil((lo + hi) / 2)];

    //  partition
    do {
        while (compareFunc(ary[i], x))
            i++;
        while (compareFunc(x, ary[j]))
            j--;
        if (i <= j) {
            let tmp = ary[i];
            ary[i] = ary[j];
            ary[j] = tmp;
            i++;
            j--;
        }
    } while (i <= j);

    //  recursion

    if (lo < j)
        SORT_ARRAY(ary, compareFunc, lo, j);
    if (i < hi)
        SORT_ARRAY(ary, compareFunc, i, hi);
};

let tV0=new Vector2();
let tV1=new Vector3();
let tV2=new Vector3();
let tV3=new Vector3();
let tR0=new Raycaster();
let tM0=new Matrix4();
let tP0=new Plane();

/**
 * 常用方法
 */
export default class Utils {
    constructor() {
        console.warn('do not instantiate class Utils,use the static method provided!');
    }

    /**
     * 均匀取值
     * @param nodes {[Number]}
     */
    static averageDivide(nodes){
        let ta=[...nodes];
        Utils.quickSort(ta,COMPARE_VALUE_DECREASE);
        let s=null,e=null;
        let dta=0;
        for(let i=0;i<ta.length-1;i++){
            let c=ta[i],n=ta[i+1];
            let d=n-c;
            if(d>=dta){
                dta=d;
                s=c;
                e=n;
            }
        }
        return (s+e)*0.5;
    }

    /**
     * 获取字节长度
     * @param str
     * @returns {number}
     */
    static byteLengthOfString(str) {
        let byteLength = 0;
        if (str === undefined || str.length === 0)return 0;
        for (let i = 0; i < str.length; i++) {
            if (str.charCodeAt(i) > 255) {
                byteLength += 2;
            } else {
                byteLength++;
            }
        }
        return byteLength;
    }

    /**
     * 值限制
     * @param val {number} 值
     * @param lo {number} 下限
     * @param hi {number} 上限
     * @returns {number}
     */
    static clamp(val,lo,hi){
        return lo<hi?Math.max(Math.min(val,hi),lo):Math.max(Math.min(val,lo),hi);
    }

    /**
     * (常用类型或实现了.clone()的任意类型对象)隐式复制/克隆（需要频繁复制时应采用显式的方法）
     * @param source
     * @returns {Quaternion|*|[]|Euler|Vector4|Vector3|Vector2|Color|Matrix4|Matrix3}
     */
    static copy(source) {
        switch (Utils.typeOf(source)) {
            case 'number':
                return source;
            case 'string':
                return source;
            case 'boolean':
                return source;
            case 'symbol':
                return source;
            case 'undefined':
                return source;
            case 'null':
                return source;
            case 'vector2':
                return new Vector2().copy(source);
            case 'vector3':
                return new Vector3().copy(source);
            case 'vector4':
                return new Vector4().copy(source);
            case 'matrix3x3':
                return new Matrix3().copy(source);
            case 'matrix4x4':
                return new Matrix4().copy(source);
            case 'color':
                return new Color().copy(source);
            case 'quaternion':
                return new Quaternion().copy(source);
            case 'euler':
                return new Euler().copy(source);
            case 'array':
                let tp = [];
                for (let i = 0; i < source.length; i++) {
                    tp[i] = Utils.copy(source[i]);
                }
                return tp;
            default:
                try {
                    if (Utils.typeOf(source.clone) === 'function') {
                        return source.clone();
                    } else {
                        let tp = {};
                        for (let key in source) {
                            tp['' + key + ''] = Utils.copy(source['' + key + '']);
                        }
                        return tp;
                    }
                } catch (err) {
                    console.log('' + err.message + ',cannot copy the value of this source!');
                    return source;
                }
        }
    }

    /**
     * 克隆gltf模型包
     * @param gltf {gltf}
     * @returns {gltf}
     */
    static cloneGLTF(gltf){
        let clone = {
            animations: gltf.animations,
            scene: gltf.scene.clone(true)
        };
        let skinnedMeshes = {};
        gltf.scene.traverse(node => {
            if (node.isSkinnedMesh) {
                skinnedMeshes[node.name] = node;
            }
        });
        let cloneBones = {};
        let cloneSkinnedMeshes = {};
        clone.scene.traverse(node => {
            if (node.isBone) {
                cloneBones[node.name] = node;
            }
            if (node.isSkinnedMesh) {
                cloneSkinnedMeshes[node.name] = node;
            }
        });
        for (let name in skinnedMeshes) {
            let skinnedMesh = skinnedMeshes[''+name+''];
            let skeleton = skinnedMesh.skeleton;
            let cloneSkinnedMesh = cloneSkinnedMeshes[''+name+''];
            let orderedCloneBones = [];
            for (let i = 0; i < skeleton.bones.length; ++i) {
                let cloneBone = cloneBones[skeleton.bones[i].name];
                orderedCloneBones.push(cloneBone);
            }
            cloneSkinnedMesh.bind(
                new Skeleton(orderedCloneBones, skeleton.boneInverses),
                cloneSkinnedMesh.matrixWorld);
        }
        return clone;
    }

    /**
     * 终止timer对象的协程
     * @param timer
     */
    static cancelAnimationFrame(timer){
        if(timer===null||timer===undefined)return;
        const tp=Utils.typeOf(timer);
        if(tp==='string'||tp==='number'){
            cancelAnimationFrame(timer);
            return;
        }
        if(!timer.hasOwnProperty('id'))return;
        cancelAnimationFrame(timer.id);
        delete timer.id;
    }

    /**
     * 获取嵌套数组所有元素
     * @param array {Array|Object} 原始数组/对象
     * @param out {Array} 输出数组
     * @param getChild {Function} 子级获取(过滤)方法
     */
    static decomposeArray(array,out,getChild=null){
        out.length=0;
        let dps=(array,targetArray)=>{
            let ary=getChild?getChild(array):array;
            if(!ary){
                targetArray.push(array);
                return;
            }
            for(let i=0;i<ary.length;i++){
                let tp=getChild?getChild(ary[i]):ary[i];
                if(!Array.isArray(tp)){
                    targetArray.push(ary[i]);
                }else{
                    dps(ary[i],targetArray);
                }
            }
        };
        dps(array,out);
    }

    /**
     * 均匀划分取位
     * @param count {Number}    间隔数量
     * @param out   {Null|Array}    输出所有位置
     * @param min   {Number}    区间最小（不含）
     * @param max   {Number}    区间最大（包含）
     * @param startDelta    {Number}    初始增量（之后1/2递减）
     * @returns {number}    返回最后间隔位置
     */
    static divideLocation(count,out=null,min=0.5,max=2,startDelta=0.5){
        let sps = [max];
        let dta = startDelta;
        let k=1;
        for (let i = 1; i < count+1; i++) {
            let t=sps[k-1]-dta;
            if(t-dta<=min){
                dta/=2;
                k=0;
                sps.push(t);
            }else{
                sps.push(t);
            }
            k++;
        }
        if(out)out.push(...sps);
        return sps[sps.length-1];
    }

    /**
     * 转换10进制数字到替代字符
     * @param {int} n 整数数字
     * @param {int} base 进制
     * @param {int} refCode 替代字符起点
     * @returns {string}
     */
    static decimalToString(n,base=26,refCode=64){
        let str='';
        while (n>0){
            let m=n%base;
            if(m===0)m=base;
            str=String.fromCodePoint(m+refCode)+str;
            n=(n-m)/base;
        }
        return str;
    }

    /**
     * 由Z向上的欧拉角参数构建Y向上的欧拉角
     * @param x
     * @param y
     * @param z
     * @param order
     * @param isDegree
     * @returns {Euler}
     */
    static eulerY(x,y,z,order='XYZ',isDegree=true){
        let fac=isDegree?D2R:1;
        let _x=x,_y=z,_z=-y,_o=order;
        switch (order) {
            case 'XYZ':
                _o='XZY';
                break;
            case 'XZY':
                _o='XYZ';
                break;
            case 'YXZ':
                _o='ZXY';
                break;
            case 'YZX':
                _o='XZY';
                break;
            case 'ZXY':
                _o='YXZ';
                break;
            case 'ZYX':
                _o='YZX';
                break;
            case 'xyz':
                _o='XZY';
                break;
            case 'xzy':
                _o='XYZ';
                break;
            case 'yxz':
                _o='ZXY';
                break;
            case 'yzx':
                _o='XZY';
                break;
            case 'zxy':
                _o='YXZ';
                break;
            case 'zyx':
                _o='YZX';
                break;
        }
        return new Euler(_x*fac,_y*fac,_z*fac,_o);
    }

    /**
     * （常用类型或实现了.equals(other)的任意类型对象）隐式相等判断（需要频繁计算时应指定类型或采用显式相等判断）
     * @param a
     * @param b
     * @param type {boolean|string}
     * @param precision
     * @returns {boolean|*}
     */
    static equal(a, b, type = false,precision=0) {
        let _type = type;
        if (_type === false) {
            let type0 = Utils.typeOf(a);
            let type1 = Utils.typeOf(b);
            if (type0 !== type1) return false;
            _type = type1;
        }
        switch (_type) {
            case 'number':
                if(!precision) return a === b;
                return Utils.zoneEqual(a,precision,b);
            case 'string':
                return a === b;
            case 'boolean':
                return a === b;
            case 'undefined':
                return true;
            case 'null':
                return true;
            case 'matrix3x3':
                for (let i = 0; i < a.elements.length; i++) {
                    if(!precision) {
                        if (a.elements[i] !== b.elements[i]) return false;
                    }else{
                        if(!Utils.zoneEqual(a.elements[i],precision,b.elements[i]))return false;
                    }
                }
                return true;
            case 'matrix4x4':
                for (let i = 0; i < a.elements.length; i++) {
                    if(!precision) {
                        if (a.elements[i] !== b.elements[i]) return false;
                    }else{
                        if(!Utils.zoneEqual(a.elements[i],precision,b.elements[i]))return false;
                    }
                }
                return true;
            case 'vector3':
                if(!precision) {
                    return a.x === b.x && a.y === b.y && a.z === b.z;
                }
                return Utils.zoneEqual(a.x ,precision, b.x) &&
                    Utils.zoneEqual(a.y ,precision, b.y) &&
                    Utils.zoneEqual(a.z ,precision, b.z);
            case 'vector2':
                if(!precision) {
                    return a.x === b.x && a.y === b.y;
                }
                return Utils.zoneEqual(a.x ,precision, b.x) &&
                    Utils.zoneEqual(a.y ,precision, b.y);
            case 'vector4':
                if(!precision) {
                    return a.x === b.x && a.y === b.y && a.z === b.z&&a.w===b.w;
                }
                return Utils.zoneEqual(a.x ,precision, b.x) &&
                    Utils.zoneEqual(a.y ,precision, b.y) &&
                    Utils.zoneEqual(a.z ,precision, b.z)&&
                    Utils.zoneEqual(a.w,precision,b.w);
            case 'symbol':
                return false;
            case 'array':
                if (a.length !== b.length) return false;
                for (let i = 0; i < a.length; i++) {
                    if (!Utils.equal(a[i], b[i],false,precision)) return false;
                }
                return true;
            case 'color':
                if(!precision) {
                    return a.r === b.r && a.g === b.g && a.b === b.b;
                }
                return Utils.zoneEqual(a.r ,precision, b.r )&&
                    Utils.zoneEqual(a.g ,precision, b.g )&&
                    Utils.zoneEqual(a.b ,precision, b.b);
            case 'quaternion':
                if(!precision) {
                    return a.x === b.x && a.y === b.y && a.z === b.z&&a.w===b.w;
                }
                return Utils.zoneEqual(a.x ,precision, b.x) &&
                    Utils.zoneEqual(a.y ,precision, b.y) &&
                    Utils.zoneEqual(a.z ,precision, b.z)&&
                    Utils.zoneEqual(a.w,precision,b.w);
            case 'euler':
                if(!precision) {
                    return a.x === b.x && a.y === b.y && a.z === b.z&&a.order===b.order;
                }
                return Utils.zoneEqual(a.x ,precision, b.x) &&
                    Utils.zoneEqual(a.y ,precision, b.y) &&
                    Utils.zoneEqual(a.z ,precision, b.z)&&
                    a.order===b.order;
            case 'object':
                let avs = Object.keys(a);
                let bvs = Object.keys(b);
                if (avs.length !== bvs.length) return false;
                for (let ak of avs) {
                    if (bvs.findIndex((bk) => bk === ak) < 0) return false;
                }
                for (let key in a) {
                    if (!Utils.equal(a['' + key + ''], b['' + key + ''])) return false;
                }
                return true;
            default:
                if (Utils.typeOf(a.equals) === 'function') {
                    return a.equals(b);
                } else {
                    return a === b;
                }
        }
    }

    /**
     * 获取3D对象所在场景
     * @param obj
     * @returns {null|Scene}
     */
    static getParentScene(obj){
        let _obj=obj;
        while (_obj.parent){
            _obj=_obj.parent;
            if(_obj instanceof Scene)return _obj;
        }
        return null;
    }

    /**
     * 获取3D对象的最高父级（向下偏移）
     * @param obj
     * @param offset 最高父级的向下偏移层级
     */
    static getTopParent(obj,offset=1){
        let _obj=obj;
        let ps=[obj];
        while (_obj.parent){
            ps.push(_obj.parent);
            _obj=_obj.parent;
        }
        let tar=ps[ps.length-1-offset];
        ps=null;
        return tar;
    }

    /**
     * 根据uuid获取场景物体
     * @param scene
     * @param uuid
     * @returns {*}
     */
    static getObjectByUuid(scene,uuid) {
        let target=null;
        let found=false;
        scene.traverse((o) => {
            if(!found){
                if (o.uuid !== undefined && o.uuid === uuid) {
                    target= o;
                    found=true;
                }
            }
        });
        return target;
    }

    /**
     * 过滤字符串中的多余空格（滤除首尾空格，滤除多个连续的空格为一个）
     * @param text {string}
     * @returns {string}
     */
    static filterSpace(text){
        V_ARRAY.length=0;
        let count=0;
        for(let s of text){
            if(s===' '){
                if(count===0){
                    V_ARRAY.push(s);
                }
                count++;
            }else{
                V_ARRAY.push(s);
                count=0;
            }
        }
        if(V_ARRAY[0]===' ')V_ARRAY.splice(0,1);
        if(V_ARRAY[V_ARRAY.length-1]===' ')V_ARRAY.splice(V_ARRAY.length-1,1);
        return V_ARRAY.join('');
    }

    /**
     * 初始化一个实现了setPrg(t)方法及其它信息的动画对象
     * @param actor {Object3D}
     * @param clip {AnimationClip}
     * @param loop
     */
    static initAnimator(actor, clip, loop=LoopRepeat) {
        let amt = {};
        amt.uuid=MathUtils.generateUUID();
        amt.root = actor;
        amt.mixer = new AnimationMixer(amt.root);
        amt.action = amt.mixer.clipAction(clip);
        amt.length = amt.action.getClip().duration;
        amt.action.loop = loop;
        amt.prg=0;
        amt.action.play();
        amt.setPrg = (prg) => {
            amt.prg =Utils.clamp(prg,0,1);
            amt.action.time = amt.prg * amt.length;
            amt.mixer.update(0);
        };
        return amt;
    }

    /**
     * 初始化一个音频控制对象
     * @param listener { AudioListener}
     * @param buffer {*}
     * @param isLoop {boolean}
     */
    static initAudio(listener,buffer,isLoop=false){
        let audio={};
        audio.content = new Audio(listener);
        audio.content.setBuffer(buffer);
        audio.content.setLoop(isLoop);
        audio.mute=false;
        audio.volume=1;
        audio.active=(bool)=>{
            audio.mute=!bool;
            if(audio.content.isPlaying){
                audio.content.setVolume(bool?audio.volume:0);
            }
        };
        audio.play=(val)=>{
            audio.volume=val;
            let vol=audio.mute?0:val;
            audio.content.setVolume(vol);
            if(!audio.content.isPlaying)audio.content.play();
        };
        audio.stop=()=>{
            if(audio.content.isPlaying)audio.content.stop();
        };
        return audio;
    }

    /**
     * 元素是否在元素序列中
     * @param e
     * @param es
     * @param compareFunc
     * @param returnIndex
     * @returns {*}
     */
    static inElements(e, es,compareFunc=COMPARE_EQUAL_DEFAULT, returnIndex=false) {
        for (let i = 0; i < es.length; i++) {
            if (compareFunc(e, es[i])) {
                return (returnIndex ? i : true);
            }
        }
        return (returnIndex ? -1 : false);
    }

    /**
     * 是否为可计算的数值
     * @param v
     * @returns {boolean}
     */
    static isComputableNumber(v){
        if(typeof v!=='number')return false;
        if(Number.isNaN(v))return false;
        return Number.isFinite(v);
    }

    /**
     * 是PC端还是移动端
     * @returns {boolean}
     */
    static isPC(){
        let agent = navigator.userAgent;
        let agents = ['Android', 'Windows Phone', 'iPhone', 'iPod', 'iPad', 'SymbianOS'];
        let isPc = true;
        for (let ag of agents) {
            if (agent.indexOf(ag) > 0) {
                isPc = false;
                break;
            }
        }
        return isPc;
    }

    /**
     * 颜色插值
     * @param p {number} 比值
     * @param from {Color} 开始颜色
     * @param to {Color} 结束颜色
     * @param out {Color} 结果输出颜色
     */
    static interpolateColor(p,from,to,out) {
        let _p = Utils.clamp(p, 0, 1);
        let r = to.r - from.r;
        let g = to.g - from.g;
        let b = to.b - from.b;

        out.r=r * _p + from.r;
        out.g=g * _p + from.g;
        out.b=b * _p + from.b;
    }

    /**
     * 间隔执行（按时间）
     * @param timeObject {object} 协程对象
     * @param timeInterval {number} 间隔时间
     * @param func_interval_return_isExit {function} 执行函数（返回是否退出的bool值）
     */
    static intervalExecuteTime(timeObject,timeInterval,func_interval_return_isExit){
        Utils.cancelExecuteDelay(timeObject);
        timeObject.time=performance.now();
        let ani=()=>{
            let now=performance.now();
            let exit=false;
            let dta=now-timeObject.time;
            if(dta>=timeInterval){
                exit=func_interval_return_isExit(dta);
                timeObject.time=now;
            }
            if(exit){
                Utils.cancelExecuteDelay(timeObject);
                return;
            }
            timeObject.t=requestAnimationFrame(ani);
        };
        ani();
    }

    /**
     * 间隔执行（按帧）
     * @param frameObject {object} 协程对象
     * @param frameInterval {number} 间隔帧数
     * @param func_interval_return_isExit {function} 执行函数（返回是否退出的bool值）
     */
    static intervalExecuteFrame(frameObject,frameInterval,func_interval_return_isExit){
        Utils.cancelExecuteDelay(frameObject);
        frameObject.time=0;
        frameObject.now=0;
        let ani=()=>{
            frameObject.now++;
            let exit=false;
            let dta=frameObject.now-frameObject.time;
            if(dta>=frameInterval){
                exit=func_interval_return_isExit(dta);
                frameObject.time=frameObject.now;
            }
            if(exit){
                Utils.cancelExecuteDelay(frameObject);
                return;
            }
            frameObject.t=requestAnimationFrame(ani);
        };
        ani();
    }

    /**
     * 判断是否为广义数组（普通数组和类型化数组）
     * @param obj
     * @returns {boolean}
     */
    static isGeneralArray(obj){
        if(typeof obj !== 'object')return false;
        if(Array.isArray(obj))return true;
        return obj[0]!==undefined;
    }

    /**
     * 循环递归退出/中断
     * @param count
     * @param call
     * @param limit
     * @returns {*}
     */
    static loopSafe(count,limit=1000,call=null){
        if(count>limit){
            if(!call) throw new Error('recursion/loop did not break out by given times!');
            call();
        }
        return count+1;
    }

    /**
     * 往复插值
     * @param start
     * @param end
     * @param t
     * @returns {*}
     */
    static pingPong(start,end,t){
        let _t=t-Math.floor(t);
        if(_t<=0.5){
            return (end-start)*_t*2+start;
        }else{
            return (end-start)*(1-_t)*2+start;
        }
    }

    /**
     * 检测字符串是否是数字
     * @param str
     * @returns {boolean}
     */
    static stringIsNumber(str) {
        if(str.charAt(0)==='.'||str.charAt(str.length-1)==='.')return false;
        let reg = /[0-9]/g;
        let tp=str;
        tp=tp.replace(reg,'');
        return (tp===''||tp==='.');
    }

    /**
     * 生成斜测投影剪裁矩阵
     * @param angle
     * @param offsetX
     * @param offsetY
     * @param offsetZ
     * @returns {Matrix4}
     */
    static makeCabinetShearMatrix(angle,offsetX,offsetY,offsetZ) {
        let mtx=new Matrix4();
        let te=mtx.elements;
        te[ 0 ] = 1;	    te[ 4 ] = 0;	    te[ 8 ] = -0.5 * Math.cos(angle);	    te[ 12 ] = offsetX;
        te[ 1 ] = 0;	    te[ 5 ] = 1;	    te[ 9 ] = -0.5 * Math.sin(angle);	    te[ 13 ] = offsetY;
        te[ 2 ] = 0;	    te[ 6 ] = 0;	    te[ 10 ] = 1;	                        te[ 14 ] = offsetZ;
        te[ 3 ] = 0;	    te[ 7 ] = 0;	    te[ 11 ] = 0;	                        te[ 15 ] = 1;
        return mtx;
    }

    /**
     * 按精度修正数字(如152.42645按精度0.05为152.45)
     * @param number {number}
     * @param normal {number}
     * @returns {string}
     */
    static normalizeNumber(number,normal=0.02){
        let dec=normal;
        let decN=dec;
        let temp=dec.toString();
        let l=temp.length;
        let dl=temp.indexOf(".");
        dl=dl===-1?0:dl;
        let fl=dl===0?0:l-dl-1;

        // let dc=Math.pow(10,l-dl-1);
        let count=Math.round(number/decN)*decN;
        return (count).toFixed(fl);
        // return (Math.round(count*dc)/dc).toFixed(fl);
    }

    /**
     * 获取临近值(索引)
     * @param dv {int} 按区间偏移计的起始值
     * @param min {int} 区间小值
     * @param max {int} 区间大值
     * @param offset {int} 目标偏移值
     * @returns {int}
     */
    static neighborValue(dv,min,max,offset=1){
        let range=max-min+1;
        let nv=dv+offset;
        do{
            nv+=range;
        }while (nv<0);
        nv%=range;
        return min+nv;
    };

    /**
     * 按序列播放（实现了.setPrg(t)的动画对象）或执行
     * @param requests {array} 存放animationFrame的序列
     * @param requestIndex {number} 要使用的animationFrame在序列中的索引
     * @param sequence_ani_s_e_sp_sCall_pCall_eCall {array} 详细如下：
     *    ani:实现了.setPrg(number)方法的动画对象或null，number为动画进度（0到1）
     *    s:动画开始节点（0到1）或计时起点
     *    e:动画结束节点（0到1）或计时终点
     *    sp:进度推进的速度
     *    sCall:开始时的回调函数，可选：返回播放速度的倍率
     *    pCall:过程中的回调函数，参数:p(0到1)为当前阶段的进度
     *    eCall:结束时的回调函数
     */
    static playSequence(requests, requestIndex, ...sequence_ani_s_e_sp_sCall_pCall_eCall) {
        if(requests[requestIndex]!==null){
            cancelAnimationFrame(requests[requestIndex]);
            requests[requestIndex] = null;
        }

        let prg = 0;
        let length = sequence_ani_s_e_sp_sCall_pCall_eCall.length;
        let index = 0;
        while (sequence_ani_s_e_sp_sCall_pCall_eCall[index] === null) {
            index++;
            if (index === length) break;
        }
        if (index === length) {
            return;
        }
        let animator = sequence_ani_s_e_sp_sCall_pCall_eCall[index][0];
        let start = sequence_ani_s_e_sp_sCall_pCall_eCall[index][1];
        let end = sequence_ani_s_e_sp_sCall_pCall_eCall[index][2];
        let delta = end - start;
        let speed = sequence_ani_s_e_sp_sCall_pCall_eCall[index][3];
        let startCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][4];
        let progressCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][5];
        let endCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][6];
        let spdR=1;
        if (startCall) spdR=startCall();
        spdR=spdR?spdR:1;
        let ani = () => {
            prg += speed*spdR;
            prg = prg < 0 ? 0 : (prg > 1 ? 1 : prg);
            let t = delta * prg + start;
            if (animator) animator.setPrg(t);
            if (progressCall) progressCall(prg);
            if (prg === 1) {
                prg = 0;
                if (endCall) endCall();
                index++;
                while (sequence_ani_s_e_sp_sCall_pCall_eCall[index] === null) {
                    index++;
                    if (index === length) break;
                }
                if (index === length) {
                    cancelAnimationFrame(requests[requestIndex]);
                    requests[requestIndex] = null;
                    return;
                }
                animator = sequence_ani_s_e_sp_sCall_pCall_eCall[index][0];
                start = sequence_ani_s_e_sp_sCall_pCall_eCall[index][1];
                end = sequence_ani_s_e_sp_sCall_pCall_eCall[index][2];
                delta = end - start;
                speed = sequence_ani_s_e_sp_sCall_pCall_eCall[index][3];
                startCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][4];
                progressCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][5];
                endCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][6];
                if (startCall) spdR=startCall();
                spdR=spdR?spdR:1;
            }
            requests[requestIndex] = requestAnimationFrame(ani);
        };
        ani();
    }

    /**
     * 快速排序
     * @param ary {Array} 要排序的序列
     * @param compareFunc {function} 序列元素值对比的函数（a,b）[return a<b；由小到大]
     */
    static quickSort(ary, compareFunc=COMPARE_VALUE_DECREASE) {
        if (ary.length > 1) {
            SORT_ARRAY(ary, compareFunc, 0, ary.length - 1);
        }
    }

    /**
     * 重设类型化数组长度
     * @param ary
     * @param len
     * @returns {Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array}
     */
    static resizeTypedArray(ary, len) {
        let nary;
        switch (ary.__proto__) {
            case Int8Array.prototype:
                nary = new Int8Array(len);
                break;
            case Uint8Array.prototype:
                nary = new Uint8Array(len);
                break;
            case Int16Array.prototype:
                nary = new Int16Array(len);
                break;
            case Uint16Array.prototype:
                nary = new Uint16Array(len);
                break;
            case Int32Array.prototype:
                nary = new Int32Array(len);
                break;
            case Uint32Array.prototype:
                nary = new Uint32Array(len);
                break;
            case Float32Array.prototype:
                nary = new Float32Array(len);
                break;
            default:
                console.warn('unsupported type!');
                nary = new Float32Array(len);
                break;
        }
        for (let i = 0; i < ary.length; i++) {
            if (nary[i] !== undefined) {
                nary[i] = ary[i];
            } else {
                break;
            }
        }
        return nary;
    }

    /**
     * 移除序列中的重复元素
     * @param ary
     * @param compareFunc
     */
    static removeDuplicated(ary, compareFunc=COMPARE_EQUAL_DEFAULT) {
        let temp=[...ary];
        ary.length=0;
        for(let t of temp){
            if(!Utils.inElements(t,ary,compareFunc))ary.push(t);
        }
    }

    /**
     * 特定区间随机数
     * @param min {number}
     * @param max {number}
     * @returns {*}
     */
    static random(min, max) {
        let delta = max - min;
        let rDelta = delta * Math.random();
        return min + rDelta;
    }

    /**
     * 初始化一个协程对象
     * @param func_t_return_break {function}
     * @param min {number} 执行间隔（ms）
     * @param max {number} 最大间隔（ms）
     * @param timerObject {null|Object}
     */
    static requestAnimationFrame(func_t_return_break,min=20,max=200,timerObject=null){
        let timer=timerObject||{};
        if(timer.hasOwnProperty('id')){
            Utils.cancelAnimationFrame(timer);
        }
        timer.info=new Float32Array([min,max,0,0,0]);
        timer.func=func_t_return_break;
        let ani=()=>{
            let info=timer.info;
            info[2]=performance.now();
            let d=info[2]-info[3];
            if(d>info[0]){
                info[3]=d>info[0]*2?info[2]:info[3]+info[0];
                d=info[2]-info[4];
                info[4]=info[2];
                if(timer.func(d>info[1]?info[1]:d)){
                    cancelAnimationFrame(timer.id);
                    return;
                }
            }
            timer.id=requestAnimationFrame(ani);
        };
        ani();
        return timer;
    }

    /**
     * 屏幕坐标转化为世界坐标
     * @param rect {object} 当前视口区域rect{left,top,width,height}
     * @param camera {Camera} 相应的相机
     * @param focus {number|Vector3} 投射深度/焦点
     * @param point {Vector2} 屏幕点（像素值或比值）
     * @param isFlat {boolean} 是否固定在焦点平面（否则沿弧面）
     * @param scale {number} 缩放
     * @param isRatio {boolean} 屏幕点是否是比值表示
     * @param out {Vector3} 输出
     * @returns {any}
     */
    static screenPointToWorld(rect, camera, focus, point, isFlat = true, scale = 1, isRatio = false,out=null) {
        let x, y;
        if (!isRatio) {
            x = point.x - rect.left;
            y = point.y - rect.top;
        }
        let rx = isRatio ? point.x : (x / rect.width);
        let ry = isRatio ? point.y : (y / rect.height);
        tV0.x = (rx * 2 - 1) * scale;
        tV0.y = (1 - ry * 2) * scale;
        let raycaster = tR0;
        camera.updateMatrixWorld();
        camera.updateProjectionMatrix();
        raycaster.setFromCamera(tV0, camera);
        let plane = tP0;
        let dir = tV1;
        tM0.identity();
        isFlat?
            camera.getWorldDirection(dir):
            dir.copy(raycaster.ray.direction);
        if(typeof focus==='number'){
            tV2.copy(dir).multiplyScalar(focus);
            tV3.copy(raycaster.ray.origin);
            tV3.add(tV2);
            plane.setFromNormalAndCoplanarPoint(dir.negate(),tV3);
        }else{
            plane.setFromNormalAndCoplanarPoint(dir.negate(),focus);
        }
        let intersect=out||new Vector3();
        raycaster.ray.intersectPlane(plane,intersect);
        return (intersect) ? intersect : false;
    }

    /**
     * 获取按序列值排序的序列号
     * @param ary
     * @param compareFunc
     * @returns {Uint16Array}
     */
    static sortIndexArray(ary,compareFunc=COMPARE_VALUE_DECREASE){
        let iAry=new Uint16Array(ary.length);
        for(let i=0;i<iAry.length;i++){
            iAry[i]=i;
        }
        let compare=(a,b)=>{
            return compareFunc(ary[a],ary[b]);
        };
        Utils.quickSort(iAry,compare);
        return iAry;
    }

    /**
     * 设置射线检测
     * @param camera
     * @param rect
     * @param event
     * @param caster
     * @param pointerOut
     * @param pointerVecOut
     */
    static setCaster(camera,rect,event,caster,pointerOut=new Vector2(),pointerVecOut=new Vector2()){
        if(event){
            if(event.isVector2){//20220417
                pointerOut.copy(event);
            }else{
                event.isMouse ?
                    pointerOut.set(event.clientX, event.clientY) :
                    pointerOut.set(event.touches[0].clientX, event.touches[0].clientY);
            }
        }
        if(pointerVecOut) {
            let rc = rect;
            let x = (pointerOut.x - rc.left) / rc.width;
            let y = (pointerOut.y - rc.top) / rc.height;
            pointerVecOut.set((x * 2) - 1, 1 - (y * 2));
        }
        if(caster&&pointerVecOut)caster.setFromCamera(pointerVecOut, camera);
    }

    /**
     * 选择并更改原数组
     * @param pool {Array} 从数组中选取，会被更改
     * @param mode {Number} 0:随机,1:首位,2:末位
     */
    static select(pool,mode=0){
        let re=null;
        if(pool.length===0)return re;
        switch (mode) {
            case 0:
                let i=Math.round(Utils.random(0,pool.length-1));
                re=pool[i];
                pool.splice(i,1);
                break;
            case 1:
                re=pool[0];
                pool.splice(0,1);
                break;
            case 2:
                re=pool[pool.length-1];
                pool.splice(pool.length-1,1);
                break;
        }
        return re;
    }

    /**
     * 获取（常用类型或实现了.type属性的任意对象）对象类型（需要频繁检测时应使用显式的方法）
     * @param object
     * @returns {string|*}
     */
    static typeOf(object) {
        if (object === null) return 'null';
        if (object === undefined) return 'undefined';
        if (typeof object === 'number') return 'number';
        if (typeof object === 'string') return 'string';
        if (typeof object === 'boolean') return 'boolean';
        if (typeof object === 'symbol') return 'symbol';
        if (Array.isArray(object)) return 'array';
        if (object.isVector3) return 'vector3';
        if (object.isVector4) return 'vector4';
        if (object.isVector2) return 'vector2';
        if (object.isMatrix3) return 'matrix3x3';
        if (object.isMatrix4) return 'matrix4x4';
        if (object.isColor) return 'color';
        if (object instanceof Quaternion) return 'quaternion';
        if (object.isEuler) return 'euler';
        if (typeof object === 'function') return 'function';
        if (object.type !== undefined) return object.type;
        return 'object';
    }

    /**
     * 由Z向上的向量参数构建Y向上的向量
     * @param x
     * @param y
     * @param z
     * @returns {Vector3}
     */
    static vectorY(x,y,z){
        return new Vector3(x,z,-y);
    }

    /**
     * 线性对照插值（节点位置对应from、to单调变化）
     * @param t {Number} 目标位置
     * @param from {Number} 起始位置
     * @param to {Number} 结束位置
     * @param node_value_ary {array} 对照数据列表：节点位置0（from），节点值0，节点位置1，节点值1，... ... ,节点位置n(to),节点值n
     * @returns {Number}
     */
    static valueLinearMapping(t,from,to,node_value_ary){
        let ascending=true;
        if(node_value_ary[0]===from&&node_value_ary[node_value_ary.length-2]===to){
            ascending=from<to;
        }else{
            console.warn('[mapValue] need more aligned data!');
            return t;
        }
        let v=Utils.clamp(t,from,to);
        for(let i=0;i<node_value_ary.length;i+=2){
            let node=node_value_ary[i];
            if(node===v){
                return node_value_ary[i+1];
            }
            let n=node_value_ary[i+2],s=0,e=0;
            let sus=ascending?(v>node&&v<n):(v<node&&v>n);
            if(sus){
                s=node_value_ary[i+1];
                e=node_value_ary[i+3];
                return s+(e-s)*(v-node)/(n-node);
            }
        }
        console.warn('[mapValue] mapping failed!');
        return t;
    }

    /**
     * 立即取消waitToExecute
     * @param delayObject
     */
    static cancelExecuteDelay(delayObject){
        if(delayObject.t===null||delayObject.t===undefined)return;
        cancelAnimationFrame(delayObject.t);
        delayObject.t = null;
        /** deprecate
        if(delayObject.o){
            clearTimeout(delayObject.o);
            delayObject.o =null;
        }else if(delayObject.t){
            cancelAnimationFrame(delayObject.t);
            delayObject.t = null;
        }
         */
    }

    /**
     * 在规定间隔内只执行一次（按帧）
     * @param frameObject {object} 存放animationFrame和帧数的对象
     * @param frameCount {number} 间隔的帧数
     * @param fuc {function} 回调函数
     * @param isHead {boolean} 首执行（false为尾执行）
     */
    static waitToExecuteFrame(frameObject, frameCount, fuc,isHead=true) {
        if(isHead) {
            if (frameObject.t !== null && frameObject.t !== undefined) {
                return;
            }
            frameObject.counter = 0;
            let ani = () => {
                frameObject.counter++;
                if (frameObject.counter > frameCount) {
                    cancelAnimationFrame(frameObject.t);
                    frameObject.t = null;
                    fuc();
                    return;
                }
                frameObject.t = requestAnimationFrame(ani);
            };
            ani();
        }else{
            if(!frameObject.counter)frameObject.counter=0;
            if (frameObject.t !== null && frameObject.t !== undefined) {
                cancelAnimationFrame(frameObject.t);
                frameObject.t = null;
            }
            let ani = () => {
                frameObject.counter++;
                if (frameObject.counter > frameCount) {
                    cancelAnimationFrame(frameObject.t);
                    frameObject.t = null;
                    frameObject.counter=0;
                    fuc();
                    return;
                }
                frameObject.t = requestAnimationFrame(ani);
            };
            ani();
        }
    }

    /**
     * 在规定间隔内只执行一次（按时间）
     * @param timeObject {object} 记录时间的对象
     * @param timeInterval {number} 间隔（毫秒）
     * @param fuc {function} 回调函数
     * @param isHead {boolean} 首执行（false为尾执行）
     */
    static waitToExecuteTime(timeObject, timeInterval, fuc,isHead=true) {
        if(isHead) {
            if (timeObject.t !== null && timeObject.t !== undefined) {
                return;
            }
            timeObject.counter = performance.now() + timeInterval;
            let ani = () => {
                if (timeObject.counter < performance.now()) {
                    cancelAnimationFrame(timeObject.t);
                    timeObject.t = null;
                    fuc();
                    return;
                }
                timeObject.t = requestAnimationFrame(ani);
            };
            ani();
        }else{
            if(!timeObject.counter)timeObject.counter=performance.now()+timeInterval;
            if (timeObject.t !== null && timeObject.t !== undefined) {
                cancelAnimationFrame(timeObject.t);
                timeObject.t = null;
            }
            let ani = () => {
                if (timeObject.counter < performance.now()) {
                    cancelAnimationFrame(timeObject.t);
                    timeObject.t = null;
                    timeObject.counter=0;
                    fuc();
                    return;
                }
                timeObject.t = requestAnimationFrame(ani);
            };
            ani();
        }
    }

    /**
     * 世界坐标转化为屏幕坐标
     * @param camera {Camera} 相应的相机
     * @param rect {object} 视口尺寸{width,height}
     * @param point {Vector3} 要投射的点
     * @param outPoint {Vector3} 输出结果
     * @returns {*|Vector3}
     */
    static worldPointToScreen(camera, rect, point, outPoint = null) {
        let halfWidth = rect.width / 2;
        let halfHeight = rect.height / 2;
        camera.updateMatrixWorld();
        camera.updateProjectionMatrix();
        let tempPoint = outPoint || new Vector3();
        tempPoint.copy(point).project(camera);
        tempPoint.x = (tempPoint.x * halfWidth) + halfWidth;
        tempPoint.y = -(tempPoint.y * halfHeight) + halfHeight;
        tempPoint.z = 0;
        return tempPoint;
    }

    /**
     * 范围相等
     * @param target {number} 与之对比的值
     * @param delta {number} 范围
     * @param value {number} 对比的值
     * @returns {boolean}
     */
    static zoneEqual(target, delta, value) {
        return (value > target - delta && value < target + delta);
    }

    /**
     * 范围重叠
     * @param min0
     * @param max0
     * @param min1
     * @param max1
     * @param threshold
     */
    static zoneOverlap(min0,max0,min1,max1,threshold=MIN_FLOAT){
        if((min0>=(min1-threshold))&&(min0<=(max1+threshold)))return true;
        if((max0>=(min1-threshold))&&(max0<=(max1+threshold)))return true;
        if((min1>=(min0-threshold))&&(min1<=(max0+threshold)))return true;
        return (max1>=(min0-threshold))&&(max1<=(max0+threshold));
    }
}